10 内部类

内部类

链接到外部内

内部类(即便是private的)可以很方便的访问到外部类中的方法和字段

为什么?

内部类秘密地创建了一个指向外围类对象的引用final Outer this$0,这样就可以访问可见成员了,而这些调用引用的细节编译器都帮我们处理了。

使用.this 和 .new

使用.this做什么?

上面说到我们访问外部类的字段和方法的时候会隐含的使用一个指向外部内对象的引用。
如果我们想要显式的使用,可以使用Outer.this的方式来获取到外部类对象,这个过程在编译时期完成。

如何new一个内部类对象?

方法是使用 外部类的对象.new 内部类

为什么是使用外部类的对象而不是外部类?

因为内部类中有一个指向外部类对象的引用,所以有内部类对象之前必须先有一个外部类对象。
注意:静态内部类不需要这样。

内部类和向上转型

什么是使用内部类结合向上转型?

直接看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public interface Destination {   
String readLabel();
}

class Parcel4 {
private class PContents implements Contents {
private int i = 11;

public int value() {
return i;
}
}

protected class PDestination implements Destination {
private String label;

private PDestination(String whereTo) {
label = whereTo;
}

public String readLabel() {
return label;
}
}

public Destination destination(String s) {
return new PDestination(s);
}

public Contents contents() {
return new PContents();
}
}

从上诉的代码中,可以看到,private和protected的内部类实现了两个接口,并且在外部类中提供了两个方法创建这两个内部类的实例并return,但是由于我们方法的返回类型是实例的接口而不是具体的实例,这就是向上转型的方式。

内部类结合向上转型有什么意义?

首先我们分析一个普通类实现接口的时候会出现怎样的问题:

1
2
3
4
5
6
7
8
9
10
11
class PDestination implements Destination {
private String label;

private PDestination(String whereTo) {
label = whereTo;
}

public String readLabel() {
return label;
}
}

注意到,这个类中除了从接口中实现的方法是public之外,其它的都是private的,我们说这个类隐藏了接口的方法实现细节,但是注意了,这只是人为的去控制的,难道我们就不能再添加其它的public的方法了吗?当然可以。并且,更关键的是PDestination这个类是可以访问的(可以new的),由于类型曝光了出来,不可避免的是可以使用“类型编程”(以后会更具体的了解)。

情况危急之时,内部类站了出来,并且解决了这些问题!当使用内部类结合向上转型的时候,最大的一个区别在于:

1
2
3
4
5
6
public class TestParcel {   
public static void main(String[] args) {
Parcel4 p = new Parcel4();
Parcel4.PContents pc = p.new PContents();//Illegal -- can’t access private class:
}
}

内部类是具有private和protected权限的,这就使得在其他类中无法直接new一个实例,只能通过外部类中暴露的公共方法获得向上转型了的接口。

匿名内部类

如何理解内部类的存在?

先看一个内部类使用的案例:

1
2
3
4
5
6
7
8
9
10
11
12
public class Parcel7b {
public Contents contents() {
return new MyContents(){
private int i = 11;
public int value() { return i; }
};
}
public static void main(String[] args) {
Parcel7b p = new Parcel7b();
Contents c = p.contents();
}
}

其实它只是下面这种写法的一个简化:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Parcel7b {
class MyContents implements Contents {
private int i = 11;
public int value() { return i; }
}
public Contents contents() {
return new MyContents();
}
public static void main(String[] args) {
Parcel7b p = new Parcel7b();
Contents c = p.contents();
}
}

进一步理解内部类

匿名内部类其实是内部类实现了一个接口(或者基类),并实现(覆盖)了它的方法后,通过向上转型使用这个内部类的方式,由于向上转型,我们只关心是实现的是哪一个,具体的实现类只会定义一次,并且这个“名字”没有任何意义,甚至可以不必存在。

匿名内部类可以是局部内部类的简化,也可以是普通内部类的一个简化。
如果是局部内部类,注意局部变量需要+final,至于原因?
后面会分析。

为什么不父类去继承或者实现?

  1. 外部类已经有父类
  2. 外部类不希望去实现其他的接口(实现接口意味着曝光更多的public方法,尽管这些方法和外部类没有任何关系)
  3. 隐藏实现的细节

使用内部类改进工厂方法模式

之前在接口中提到了工厂方法模式,说过可以有一种改进,如何改进?

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

interface Service {
void method1();
void method2();
}

interface ServiceFactory {
Service getService();
}

class Implementation1 implements Service {
private Implementation1() {
}
public void method1() {
print("Implementation1 method1");
}
public void method2() {
print("Implementation1 method2");
}
public static ServiceFactory factory = new ServiceFactory() {
public Service getService() {
return new Implementation1();
}
};
}

匿名内部类很好的声明了只会使用一次的类。

静态内部类

内部类作为外部类的一个成员,也可以是静态的。

静态内部类有哪些特性?

静态内部类行和外部内部类没什么关联,无法访问外围类的非静态成员。

静态内部类有什么用?

一个比较通常的用法是用在接口上。
接口中是禁止有任何的代码的,但是静态内部类可以作为接口的一个成员(接口中的成员自动是static和public的)

这样有什么好处?

  1. 我可以在接口中的静态内部类中实现当前的接口,给所有的实现提供一个通用的方法实现方案。
    案例如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public interface ClassInInterface {
    void howdy();

    class Test implements ClassInInterface {
    public void howdy() {
    System.out.println("Howdy!");
    }
    public static void main(String[] args) {
    new Test().howdy();
    }
    }
    }
  2. 我们通常使用的测试方法是在类中写一个main方法,但是这样有一个问题是,编译后代码会附带这一部分无用的信息。静态内部类提供了一个解决方氨:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class TestBed {
    public void f() {
    System.out.println("f()");
    }

    public static class Tester {
    public static void main(String[] args) {
    TestBed t = new TestBed();
    t.f();
    }
    }
    }

编译后会生成两个class文件,其中的TestBed$Tester文件是测试类,直接删除即可。

多层嵌套的内部类

无论嵌套多少层,内部类都能访问到之前的所有外部类成员(即便是private的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MNA {
private void f() {
}

class A {
private void g() {
}

public class B {
void h() {
g();
f();
}
}
}
}

为什么需要内部类

多重继承(类),如果不是为了解决这个问题,自然可以用其它的方式代替,但是使用内部类,还是有其它的一些不明显的好处的。

什么是闭包?

闭包也是一种为表示带有自由变量的过程而用的实用技术。
在JAVA里:闭包是一个可调用的对象,它保留了创建它的作用域的信息。
很显然,这个解释就是说内部类是JAVA里的闭包。
使用JAVA的闭包可以更安全的实现回调?

什么是回调?

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
但是,在JAVA里,没有提供指针(*)的操作方式,因为使用指针是很不安全的,可是,难道引用不就是指针吗?
其实两者有明显的区别。

指针和引用的区别,指针为何不安全?

指针本质上可以在整个OS允许的内存块上任意移动,有时候还会跨界到其他内存块上去。本质上它离机器语言太近,能够造成非常巨大的外延性破坏。一个最经典的例子就是内存践踏造成的缓冲区溢出。程序设计界已经因为指针每年损失billion级别的资源。
Java否定了这种自由性。你不能根据你现有的引用移动到内存的其他位置做任何你想做的事情。
既然JAVA里没有指针,就更加没有回调函数的感念了

那内部类如何实现回调呢?

很容易,就是使用内部类去实现一个接口,然后把这个内部类生成的对象引用作为参数传递给其它方法。

什么情况下会使用内部类去实现接口?而不是使用外部类去实现接口?

当外部类有一个父类,这个父类和要实现的接口没有任何的关系,但是他们又拥有相同的方法,那不能为了实现这个接口而把父类中的方法给覆盖了。这时,最好的解决办法就是使用内部类去实现接口。

内部类和控制框架

什么是应用控制框架?

被设计解决一个特定问题的一个类或者一组类,其实就是模板方法。

什么是模板方法?

先看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public abstract class Account {
/**
* 模板方法,计算利息数额
* @return 返回利息数额
*/
public final double calculateInterest(){
double interestRate = doCalculateInterestRate();
String accountType = doCalculateAccountType();
double amount = calculateAmount(accountType);
return amount * interestRate;
}
/**
* 基本方法留给子类实现
*/
protected abstract String doCalculateAccountType();
/**
* 基本方法留给子类实现
*/
protected abstract double doCalculateInterestRate();
/**
* 基本方法,已经实现
*/
private double calculateAmount(String accountType){
/**
* 省略相关的业务逻辑
*/
return 7243.00;
}
}

一个模板方法就是模板方法里写好整个算法、流程,至于算法中有些抽象方法交给子类去具体的实现。

控制框架中使用内部类有什么优势?

先看一个控制框架的案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public abstract class Event {
private long eventTime;
protected final long delayTime;

public Event(long delayTime) {
this.delayTime = delayTime;
start();
}

public void start() { // Allows restarting
eventTime = System.nanoTime() + delayTime;
}

public boolean ready() {
return System.nanoTime() >= eventTime;
}

public abstract void action();
}

public class Controller {
// A class from java.util to hold Event objects:
private List<Event> eventList = new ArrayList<Event>();

public void addEvent(Event c) {
eventList.add(c);
}

public void run() {
while (eventList.size() > 0)
// Make a copy so you’re not modifying the list
// while you’re selecting the elements in it:
for (Event e : new ArrayList<Event>(eventList))
if (e.ready()) {
System.out.println(e);
e.action();
eventList.remove(e);
}
}
}

控制框架中Event中的action需要由子类实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class GreenhouseControls extends Controller {
private boolean light = false;

public class LightOn extends Event {
public LightOn(long delayTime) {
super(delayTime);
}

public void action() {
// Put hardware control code here to
// physically turn on the light.
light = true;
}

public String toString() {
return "Light is on";
}
}

public class LightOff extends Event {
public LightOff(long delayTime) {
super(delayTime);
}

public void action() {
// Put hardware control code here to
// physically turn off the light.
light = false;
}

public String toString() {
return "Light is off";
}
}
}

这里一定是需要使用到内部类吗?
并不是,其实使用内部类主要是了控制类的数量,因为有大量的事件,而且多数事件的相应方法只被用到了一次,考虑的是更好地封装。

内部类的继承

继承内部类是怎样的?
内部类由于在被实例化的时候,会隐藏一个指定外部类对象的引用,因此在继承内部类的时候,比较特别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class WithInner {   
class Inner {}
}
public class InheritInner extends WithInner.Inner
{
//! InheritInner() {}
// Won’t compile
InheritInner(WithInner wi) {
wi.super();
}
public static void main(String[] args) {
WithInner wi = new WithInner();
InheritInner ii = new InheritInner(wi);
}
}

上面表示:在继承内部类是,要定义一个带有外部类参数的构造器,并调用外部类.super().
至于原因,和JVM有关,暂不分析。

内部类的覆盖

内部类作为一个特殊的成员,能被覆盖吗?
看下面的这个案例分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Egg {   
private Yolk y;
protected class Yolk { public Yolk() { print("Egg.Yolk()"); }
}
public Egg() {
print("New Egg()");
y = new Yolk();
}
}
public class BigEgg extends Egg {
public class Yolk {
public Yolk() {
print("BigEgg.Yolk()");
}
}
public static void main(String[] args) {
new BigEgg();
}
}
输出:
New Egg()
Egg.Yolk()

由此可见,继承后,两个内部类即使名字一样,但是他们没有任何的关系,是两个独立的内部类,处于不同的命名空间。
如果想要实现内部类的继承,需要同时指定内部类的继承,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Egg {   
private Yolk y;
protected class Yolk { public Yolk() { print("Egg.Yolk()"); }
}
public Egg() {
print("New Egg()");
y = new Yolk();
}
}
public class BigEgg extends Egg {
public class Yolk extends Egg.Yolk{
public Yolk() {
print("BigEgg.Yolk()");
}
}
public static void main(String[] args) {
new BigEgg();
}
}
输出:
New Egg()
BigEgg.Yolk()

思考一下,为什么这里的内部类继承不需要定义特殊构造器?
结果的答案在于这里的BigEgg extends Egg。

局部内部类

什么是局部内部类?
声明在方法或者作用域中的内部类,由于不属于外部类的成员了,也就没有访问说明符,外部类看不见内部类,除此之外,和普通的内部类没有区别,也就是说,局部内部类还是有外部类的引用,可以访问到外部类的字段方法。

还需要注意的是局部内部类访问方法的局部变量,要求局部变量是final的,为什么?

因为局部变量是存放在JVM的栈区的,而当方法结束的时候,栈区中保存的局部变量就会消失,但是这时候内部类的生命周期还没有结束,因此可能会有数据不一致的问题,为了解决这个问题,必须声明内部类中使用的局部变量是final的,也就是不可变。

内部类反编译分析

私用内部类

  • 在内部类中偷偷摸摸的创建了包可见构造器,从而使外部类获得了创建权限。
  • 创建了一个指向外围类对象的引用final Outer this$0,这样就可以访问可见成员了。
  • 在外部类中偷偷摸摸的创建了访问私有变量的静态方法,从而使内部类获得了访问权限。这样,类中定义的内部类无论私有,公有,静态都可以被包围它的外部类所访问。

静态内部类

  • 少了一个指向外围类对象的引用final Outer this$0; 也就是说静态内部类无法得到其外围类对象的引用,那么自然也就无法访问外围类的非静态成员了。

局部内部类

在方法里定义的内部类叫做局部内部类

  • 在外部类中没有创建访问私有变量的静态方法,因此无法访问外部内的私用变量。
  • 对Inner类的反射发现,Inner类内部多了一个对beep变量的备份隐藏域:final int val$i,所以可以使用外部方法的final变量。

effectively final

A variable or parameter whose value is never changed after it is initialized is effectively final
.
java8出现,不需要显示的+final,就可以在局部内部类中使用(定义后值从来没有发生变化)的变量。但是如果判断变化了,就会报错。

匿名内部类

没有构造器,使用的时候把参数传给超类构造器。

本文标题:10 内部类

文章作者:Sun

发布时间:2018年10月16日 - 16:10

最后更新:2018年12月12日 - 16:12

原始链接:https://sunyi720.github.io/2018/10/16/THING IN JAVA/10 内部类/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。